using System;


namespace BaseLib
{
    public enum YieldInstructionType
    {
        /// Special instruction, 
        SpecialBegin,

        ReturnValue,
        Coroutine,

        Reserved1,
        Reserved2,
        Reserved3,
        Reserved4,
        Reserved5,
        Reserved6,
        Reserved7,
        Reserved8,
        Reserved9,
        Reserved10,

        SpecialEnd,

        Custom,
    }

    public interface IYieldInstruction : IRefCounter
    {
        bool IsDone();

        void Update(float fTime);

        void Pause(float fTime);
        void Resume(float fTime);

        void Stop();

        YieldInstructionType GetInstructionType();

        void Start(float fTime);
    }

    public interface ITimeoutable
    {
        bool IsTimeout();
    }

    ///-----
    public abstract class YieldInstruction : IYieldInstruction, IPoolable
    {
        protected int _nRefCount = 0;

        public int GetRef() { return _nRefCount; }
        public void IncRef() { _nRefCount++; }

        public void DecRef()
        {
            Debugger.Assert(_nRefCount > 0);
	        _nRefCount--; 

            if ( _nRefCount == 0 )  _onRelease();
        }

        /// ---
        /// Be implement this to start a instruction.
        virtual public void Start(float fTime) { }
        /// Will be called at each frame
        virtual public void Update(float fTime) { }
        /// Pause this instruction
        virtual public void Pause(float fTime) { }
        /// Resume this instruction
        virtual public void Resume(float fTime) { }
        /// Stop this instruction
        virtual public void Stop() {}

        virtual public bool IsDone() { return true; }

        /* 
         Note: will be called when a instruction is can be freed. When a instruction can be freed? 
         See example below, the first instruction object will be freed before the second instruction's Start() be called.
         
         IEnumerator _testCoroutine()
         {
               yield return CoroutineMgr.WaitTime();        /// First instruction
               ......                                                       /// First instruction's Stop() will be called before this line of code.
               ......   
               ......                                                       
               yield return CoroutineMgr.WaitTime();        /// Second instruction. First instruction's _onRelease() will be called after this line of code, but before Second 
                                                                            /// instruction's Start().
         } 
         
         */
        virtual protected void _onRelease() { }

        /// --- IPoolable ---
        virtual public void Destroy() {}

        virtual public YieldInstructionType GetInstructionType() { return YieldInstructionType.Custom; }
    }

    ///-----
    internal class _ReturnValue : YieldInstruction
    {
        public object returnValue;

        override protected void _onRelease() { returnValue = null; CoroutineMgr._Instance._FreeReturnValue(this); }

        override public YieldInstructionType GetInstructionType() { return YieldInstructionType.ReturnValue; }
    }

    ///-----
    internal class _WaitConditionObj : YieldInstruction, ITimeoutable
    {
        internal float _fDestTime;
        internal float _fCurTime;
        internal float _fTimeOut;
        internal float _fPausedTime;

        protected ICondition _condition;
        protected System.Action _updateHandler;

        override public void Start(float fTime)
        {
            _fDestTime = fTime + _fTimeOut;
            _fPausedTime = 0;
        }

        override public void Pause(float fTime) 
        {
            _fPausedTime = fTime;
        }

        override public void Resume(float fTime) 
        {
            _fDestTime = _fDestTime + (fTime - _fPausedTime);
            _fPausedTime = 0;
        }

        public void SetParams(ICondition conditionObj, float fTimeOut, System.Action updateHandler)
        {
            _fTimeOut = fTimeOut;
            _condition = conditionObj;
            _updateHandler = updateHandler;
        }

        override public bool IsDone() { return _condition.IsTrue() || _fCurTime >= _fDestTime; }

        override public void Update(float fTime)
        {
            if (_updateHandler != null) _updateHandler();
            _fCurTime = fTime;
        }

        public bool IsTimeout() { return _fCurTime >= _fDestTime; }
    }

    ///------
    internal class _WaitTime : YieldInstruction
    {
        internal float _fDestTime;
        internal float _fCurTime;
        internal float _fTimeOut;
        internal float _fPausedTime;

        public _WaitTime() { }

        override public void Start(float fTime)
        {
            _fDestTime = fTime + _fTimeOut;
            _fPausedTime = 0;
        }

        public void SetParams(float fTimeOut = 0.0f)
        {
            _fTimeOut = fTimeOut;
        }

        override public void Update(float fTime)
        {
            _fCurTime = fTime;
        }

        override public void Pause(float fTime)
        {
            _fPausedTime = fTime;
        }

        override public void Resume(float fTime)
        {
            _fDestTime = _fDestTime + (fTime - _fPausedTime);
            _fPausedTime = 0;
        }

        override public bool IsDone()
        {
            return _fCurTime >= _fDestTime;
        }

        protected override void _onRelease()
        {
            CoroutineMgr._Instance._FreeTimeWaiter(this);
        }
    }
    ///------
    internal class _WaitConditionFunc : YieldInstruction, ITimeoutable
    {
        internal float _fDestTime;
        internal float _fCurTime;
        internal float _fTimeOut;
        internal float _fPausedTime;

        protected Func<bool> _waitConditionFunc;
        protected System.Action _updateHandler;

        override public void Start(float fTime)
        {
            _fDestTime = fTime + _fTimeOut;
            _fPausedTime = 0;
        }

        override public void Pause(float fTime)
        {
            _fPausedTime = fTime;
        }

        override public void Resume(float fTime)
        {
            _fDestTime = _fDestTime + (fTime - _fPausedTime);
            _fPausedTime = 0;
        }

        public void SetParams(Func<bool> waitConditionFunc, float fTimeOut, System.Action updateHandler)
        {
            _fTimeOut = fTimeOut;
            _waitConditionFunc = waitConditionFunc;
            _updateHandler = updateHandler;
        }

        override public bool IsDone() { return _waitConditionFunc() || _fCurTime >= _fDestTime; }

        override public void Update(float fTime)
        {
            if(_updateHandler != null) _updateHandler();
            _fCurTime = fTime;
        }

        public bool IsTimeout() { return _fCurTime >= _fDestTime; }

        protected override void _onRelease()
        {
            _waitConditionFunc = null;
            _updateHandler = null;

            CoroutineMgr._Instance._FreeConditionWaiter(this);
        }
    }

    ///------
    internal class _WaitSignal : YieldInstruction, ITimeoutable
    {
        internal float _fDestTime;
        internal float _fCurTime;
        internal float _fTimeOut;
        internal float _fPausedTime;

        protected Signal _signal;
        protected System.Action _updateHandler;

        protected bool _bIsDone;

        override public void Start(float fTime)
        {
            _fDestTime = fTime + _fTimeOut;
            _fPausedTime = 0;
        }

        public void SetParams(Signal signal, float fTimeOut, System.Action updateHandler)
        {
            _fTimeOut = fTimeOut;
            _signal = signal;
            _updateHandler = updateHandler;

            _signal.Connect(_onSignal);
        }

        override public void Pause(float fTime)
        {
            _fPausedTime = fTime;
        }

        override public void Resume(float fTime)
        {
            _fDestTime = _fDestTime + (fTime - _fPausedTime);
            _fPausedTime = 0;
        }

        override public bool IsDone() 
        {
            return _bIsDone || _fCurTime >= _fDestTime; 
        }

        override public void Update(float fTime)
        {
            if (_updateHandler != null) _updateHandler();
            _fCurTime = fTime;
        }

        public bool IsTimeout() 
        {
            if (_fCurTime >= _fDestTime)
            {
                if (_signal.IsConnected()) _signal.Disconnect();
                return true;
            }
            else return false;
        }

        public override void Stop()
        {
            if (_signal != null)
            {
                if (_signal.IsConnected()) _signal.Disconnect();
                _signal = null;
            }
            _updateHandler = null;
            _bIsDone = false;
        }

        protected override void _onRelease()
        {
            Debugger.Assert(_signal == null);
            Debugger.Assert(_updateHandler == null);

            CoroutineMgr._Instance._FreeSignalWaiter(this);
        }
    
        protected void _onSignal()
        {
            if(_signal.IsConnected()) _signal.Disconnect();
            _bIsDone = true;
        }
    }

    ///-----
    internal class _WaitSignal<T> : YieldInstruction, ITimeoutable
    {
        internal float _fDestTime;
        internal float _fCurTime;
        internal float _fTimeOut;
        internal float _fPausedTime;

        protected Signal<T> _signal;
        protected System.Action _updateHandler;

        protected bool _bIsDone;

        override public void Start(float fTime)
        {
            _fDestTime = fTime + _fTimeOut;
            _fPausedTime = 0;
        }

        public void SetParams(Signal<T> signal, float fTimeOut, System.Action updateHandler)
        {
            _fTimeOut = fTimeOut;
            _signal = signal;
            _updateHandler = updateHandler;

            _bIsDone = false;

            _signal.Connect(_onSignal);
        }

        override public void Pause(float fTime)
        {
            _fPausedTime = fTime;
        }

        override public void Resume(float fTime)
        {
            _fDestTime = _fDestTime + (fTime - _fPausedTime);
            _fPausedTime = 0;
        }

        override public bool IsDone() { return _bIsDone || _fCurTime >= _fDestTime; }

        override public void Update(float fTime)
        {
            if (_updateHandler != null) _updateHandler();
            _fCurTime = fTime;
        }

        public bool IsTimeout() { return _fCurTime >= _fDestTime; }

        public override void Stop()
        {
            if(_signal != null)
            {
                if (_signal.IsConnected()) _signal.Disconnect();
                _signal = null;
            }
            _updateHandler = null;
            _bIsDone = false;            
        }

        protected void _onSignal(T code)
        {
            _bIsDone = true;
        }
    }

    ///------
    internal class _WaitFrameEnd : YieldInstruction
    {
        public _WaitFrameEnd() { }

        override public void Update(float fTime) { }
        override public bool IsDone() { return true; }

        protected override void _onRelease()
        {
            CoroutineMgr._Instance._FreeFrameWaiter(this);
        }
    }
}

